/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using EnvDTE;
using VSLangProj;
using VSLangProj80;
using System.IO;
using DaveSexton.DocProject.Resources;

namespace DaveSexton.DocProject
{
	public class VSAnyProject : System.ComponentModel.Component, IAnyProject
	{
		#region Public Properties
		public object Project
		{
			get
			{
				return project;
			}
		}

		public object VSProject
		{
			get
			{
				return vsProject;
			}
		}

		public virtual string Name
		{
			get
			{
				return project.Name;
			}
		}

		public virtual string UniqueName
		{
			get
			{
				return project.UniqueName;
			}
		}

		public virtual string Directory
		{
			get
			{
				return PathFormat.EnsureDirectory(Path.GetDirectoryName(project.FullName));
			}
		}

		public virtual string FilePath
		{
			get
			{
				return project.FullName;
			}
		}

		public BufferedSettingsDictionaryBase<string, string> Settings
		{
			get
			{
				if (settings == null)
				{
					settings = new GlobalsSettingsDictionary(globals, System.Globalization.CultureInfo.InvariantCulture, StringComparer.OrdinalIgnoreCase);
					settings.UseBuffer = false;
				}

				return settings;
			}
		}

		public virtual IProjectOutput Output
		{
			get
			{
				if (output == null)
					output = CreateOutput();

				return output;
			}
		}

		public IProjectProperties Properties
		{
			get
			{
				if (properties == null)
					properties = new VSProjectProperties(this, false);

				return properties;
			}
		}

		public IProjectProperties ConfigurationProperties
		{
			get
			{
				if (configurationProperties == null)
					configurationProperties = new VSProjectProperties(this, true);

				return configurationProperties;
			}
		}

		public virtual bool IsVisualBasic
		{
			get
			{
				return project.Kind == VSLangProj.PrjKind.prjKindVBProject || project.Kind == VSLangProj2.PrjKind2.prjKindSDEVBProject;
			}
		}

		public virtual bool IsCSharp
		{
			get
			{
				return project.Kind == VSLangProj.PrjKind.prjKindCSharpProject || project.Kind == VSLangProj2.PrjKind2.prjKindSDECSharpProject;
			}
		}

		public virtual bool IsJSharp
		{
			get
			{
				return project.Kind == VSLangProj2.PrjKind2.prjKindVJSharpProject;
			}
		}

		public virtual bool IsSaved
		{
			get
			{
				return project.Saved && settings.Count == 0;
			}
			set
			{
				project.Saved = value;
			}
		}
		#endregion

		#region Private / Protected
		[CLSCompliant(false)]
		protected Globals Globals
		{
			get
			{
				return globals;
			}
		}

		[CLSCompliant(false)]
		protected ReferencesEvents ReferencesEvents
		{
			get
			{
				if (refEvents == null)
					// Cache the events class so that the handlers aren't garbage collected early
					refEvents = vsProject.Events2.ReferencesEvents;

				return refEvents;
			}
		}

		[CLSCompliant(false)]
		protected internal References References
		{
			get
			{
				if (references == null)
					references = vsProject.References;

				return references;
			}
		}

		[CLSCompliant(false)]
		protected internal Project ProjectBase
		{
			get
			{
				return project;
			}
		}

		private VSProjectProperties properties, configurationProperties;
		private IProjectOutput output;
		private ReferencesEvents refEvents;
		private References references;
		private GlobalsSettingsDictionary settings;
		private readonly Globals globals;
		private readonly Project project;
		private readonly VSProject2 vsProject;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="VSAnyProject" /> class.
		/// </summary>
		internal VSAnyProject(VSProject2 vsProject2)
		{
			this.vsProject = vsProject2;

			project = vsProject2.Project;
			globals = project.Globals;
		}
		#endregion

		#region Methods
		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand,
			Flags = System.Security.Permissions.SecurityPermissionFlag.Infrastructure)]
		public override object InitializeLifetimeService()
		{
			return null;
		}

		public bool Matches(object other)
		{
			Project projectBase = other as Project;

			if (projectBase == null)
			{
				VSProject2 project2 = other as VSProject2;

				if (project2 != null)
					projectBase = project2.Project;
			}

			if (projectBase != null && UniqueNameEquals(projectBase))
				return true;

			IAnyProject anyProj = other as IAnyProject;

			return anyProj != null && Matches(anyProj);
		}

		[System.Diagnostics.DebuggerHidden]
		private bool UniqueNameEquals(Project projectBase)
		{
			try
			{
				return string.Equals(projectBase.UniqueName, project.UniqueName, StringComparison.Ordinal);
			}
			catch (System.Runtime.InteropServices.COMException)
			// occurred in testing when VS was closing since the "project" variable can no longer be accessed
			{
				// NOTE: comparing reference equality doesn't work in this case, so we'll just return false since the solution is closing anyway
				return false;
			}
		}

		public virtual bool Matches(IAnyProject other)
		{
			VSAnyProject vsOther = other as VSAnyProject;

			return vsOther != null && (this == vsOther || this.ProjectBase == vsOther.ProjectBase);
		}

		public virtual int GetMatchCode()
		{
			return (project == null) ? 0 : project.GetHashCode();
		}

		public virtual void AddReference(object projectReference)
		{
			vsProject.References.AddProject(ProjectFactory.GetVSProject(projectReference));
		}

		/// <remarks>This method does not ensure that the specified <paramref name="path"/> is a project item or that it exists.</remarks>
		/// <param name="path">Absolute or relative path to the file or directory.</param>
		public virtual IProjectItem GetItem(string path)
		{
			return new VSProjectItem(project, new Uri(Directory), path, false);
		}

		/// <param name="path">Absolute or relative path to the directory.</param>
		public virtual IProjectItem GetFolder(string path)
		{
			return new VSProjectItem(project, new Uri(Directory), path, true);
		}

		public void Save()
		{
			if (settings.Count > 0 && settings.CanCommit)
			{
				settings.Commit();
				System.Diagnostics.Debug.WriteLine("Committed project settings before saving.");
			}

			if (!project.Saved)
				project.Save(null);
		}

		public void Refresh()
		{
			vsProject.Refresh();
		}

		public void WriteProperties(string outputFile, bool configuration, bool append)
		{
			ProjectPropertyWriter.WriteProperties(Name, ((VSProjectProperties) ((configuration) 
				? ConfigurationProperties : Properties)).Properties, outputFile, append);
		}

		protected virtual IProjectOutput CreateOutput()
		{
			return new VSProjectOutput(this);
		}

		public override string ToString()
		{
			return "{" + Name + "}";
		}
		#endregion
	}
}